package com.zh.webflux.util;

import org.springframework.http.codec.multipart.Part;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

import static com.zh.webflux.util.MyDataBufferUtil.dataBufferToString;


/**
 * @author ZH
 * @date 16:20 2020/12/29
 */
public final class RequestParamWrapper {

    public static MultiValueMap<String, String> toMultiValueMap(MultiValueMap<String, Part> multipartData) {
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();

        Mono<Void> mono = Flux.fromIterable(multipartData.values()).flatMap(value -> {
            return Flux.fromIterable(value).flatMap(item -> {
                return dataBufferToString(item.content()).map(e -> {
                    map.add(item.name(), e);
                    return 0;
                });
            });
        }).then();

        try {
            CompletableFuture.runAsync(() -> {
                mono.block();
            }).get();
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }

        return map;
    }

    public static <T> T wrapper(final MultiValueMap<String, String> paramMap, Class<T> clazz) {
        Set<String> names = paramMap.keySet();

        try {
            T obj = clazz.getDeclaredConstructor().newInstance();

            if (names.isEmpty()) {
                return obj;
            }

            Map<String, Method> methods = getMethodsWithNamePrefixSet(clazz);

            for (String paramName : names) {
                List<String> paramValue = paramMap.get(paramName);

                Method method = methods.get(paramName);
                if (method == null) continue;

                method.setAccessible(true);
                try {
                    Class<?> parameterType = method.getParameterTypes()[0];

                    Object value = null;
                    if (parameterType == List.class) {
                        ParameterizedType fullParameterType = (ParameterizedType) method.getGenericParameterTypes()[0];
                        Type argType = fullParameterType.getActualTypeArguments()[0];

                        value = paramValue.stream().map(v -> toValue(v, argType.getClass())).collect(Collectors.toList());
                    } else {
                        value = toValue(paramValue.get(0), parameterType);
                    }
                    if (value != null) {
                        method.invoke(obj, value);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            return obj;
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
                 NoSuchMethodException e) {
            throw new RuntimeException(e.getCause());
        }
    }

    private static Object toValue(String value, Class<?> parameterType) {
        if (value == null || value.trim().isEmpty()) {
            return null;
        }

        if (parameterType == Boolean.class) {
            return Boolean.parseBoolean(value);
        } else if (parameterType == Byte.class) {
            return Byte.parseByte(value);
        } else if (parameterType == Short.class) {
            return Short.parseShort(value);
        } else if (parameterType == Integer.class) {
            return Integer.parseInt(value);
        } else if (parameterType == Long.class) {
            return Long.parseLong(value);
        } else if (parameterType == Float.class) {
            return Float.parseFloat(value);
        } else if (parameterType == Double.class) {
            return Double.parseDouble(value);
        } else if (parameterType == Character.class) {
            return value.charAt(0);
        } else if (parameterType == String.class) {
            return StringUtils.hasText(value.trim()) ? value.trim() : null;
        }
        return null;
    }

    private static Map<String, Method> getMethodsWithNamePrefixSet(Class<?> clazz) {
        Method[] methods = clazz.getMethods();

        return Arrays.stream(methods)
                .filter(e -> e.getName().startsWith("set") && e.getParameterTypes().length == 1)
                .collect(Collectors.toMap(method -> {
                    String name = method.getName().substring(3);
                    return name.substring(0, 1).toLowerCase() + name.substring(1);
                }, method -> method));
    }


}
